package sgsi.controller;

import java.io.IOException;
import java.io.PrintWriter;
import java.util.ArrayList;
import java.util.List;
import java.util.Set;

import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;

import net.sf.json.JSONObject;

import org.apache.commons.lang.StringUtils;
import org.springframework.validation.ObjectError;
import org.springframework.web.bind.ServletRequestDataBinder;
import org.springframework.web.servlet.ModelAndView;

import sgsi.controller.dao.AlmacenDao;
import sgsi.controller.dao.AreaDao;
import sgsi.controller.dao.PrioridadDao;
import sgsi.controller.dao.RangoDeTemperaturaDao;
import sgsi.controller.dao.TipoDeArticuloDao;
import sgsi.controller.helper.GestionarAreaHelper;
import sgsi.model.comandos.BuscarAreaComando;
import sgsi.model.comandos.NuevaAreaComando;
import sgsi.model.entidades.Prioridad;
import sgsi.model.entidades.RangoDeTemperatura;
import sgsi.model.entidades.TipoDeArticulo;
import sgsi.model.entidades.espacios.Area;
import sgsi.view.json.VistaAreaJson;
import sgsi.view.json.VistaComboJson;

/**
 * Clase controladora creada para atender acciones relativas a la gestion de
 * Areas.
 * 
 *@See SgsiMultiActionController
 * 
 */
public class GestionarAreas extends SgsiMultiActionController {

	private AreaDao areaDao;
	private TipoDeArticuloDao tipoDeArticuloDao;
	private AlmacenDao almacenDao;
	private RangoDeTemperaturaDao rangoDeTemperaturaDao;
	private PrioridadDao prioridadDao;

	/**
	 * Acci&oacute:n para el seteo de atributos iniciales para la pantalla de
	 * nueva/ edici&oacute:n de un area.
	 * <p>
	 * Crea una nueva instacia del comando <code>NuevaAreaComando</code> y la
	 * asocia a la pantalla.
	 * 
	 * @param req
	 *            Objeto HttpServletRequest que modela la peticion por parte de
	 *            la capa de presentaci&oacute;n.
	 * @param resp
	 *            Objeto HttpServletResponse que modela la respuesta del
	 *            servidor.
	 * @return ModelAndView Objecto que modela la Vista a retornar para la capa
	 *         de presentacion: en este caso particular es la pantalla de
	 *         <code>nuevaArea.html</code>.
	 * 
	 * @see NuevaAreaComando
	 */
	public ModelAndView cargaInicialNuevo(HttpServletRequest req, HttpServletResponse resp) {
		NuevaAreaComando nuevaAreaComando = new NuevaAreaComando();
		nuevaAreaComando.setTipoDeArticulosSoportados(tipoDeArticuloDao.obtenerTodos());
		nuevaAreaComando.setAlmacenes(almacenDao.obtenerTodos());
		nuevaAreaComando.setRangosDeTemperatura(rangoDeTemperaturaDao.obtenerTodos());
		nuevaAreaComando.setPrioridades(prioridadDao.obtenerTodos());
		return new ModelAndView("nuevaArea.html", "command", nuevaAreaComando);
	}

	/**
	 * Accion para el seteo de atributos iniciales para la busqueda de
	 * &aacute;reas.
	 * <p>
	 * Crea una nueva instacia del comando <code>BuscarAreaComando</code> y la
	 * asocia a la pantalla.
	 * 
	 * @param req
	 *            Objeto HttpServletRequest que modela la peticion por parte de
	 *            la capa de presentaci&oacute;n.
	 * @param resp
	 *            Objeto HttpServletResponse que modela la respuesta del
	 *            servidor.
	 * 
	 * @return ModelAndView Objecto que modela la Vista a retornar para la capa
	 *         de presentacion: en este caso particular es la pantalla de
	 *         <code>actualizarArea.html</code>.
	 * 
	 * @see BuscarAreaComando
	 */
	public ModelAndView cargaInicialActualizar(HttpServletRequest req, HttpServletResponse resp) {
		BuscarAreaComando buscarAreaComando = new BuscarAreaComando();
		buscarAreaComando.setAlmacenes(almacenDao.obtenerTodos());
		return new ModelAndView("actualizarArea.html", "command", buscarAreaComando);
	}

	/**
	 * Acci&oacute:n para la creacion de una nueva &aacute;rea.
	 * <p>
	 * Recupera las propiedades de la nueva intancia desde el comando
	 * <code>NuevaAreaComando</code> y lo guarda en la base de datos.
	 * 
	 * @param req
	 *            Objeto HttpServletRequest que modela la peticion por parte de
	 *            la capa de presentaci&oacute;n.
	 * @param resp
	 *            Objeto HttpServletResponse que modela la respuesta del
	 *            servidor.
	 * @return ModelAndView Objecto que modela la Vista a retornar para la capa
	 *         de presentacion: si la creacion fue exitosa retorna a la pantalla
	 *         principal <code>home.html</code>, en otro caso, retorna a la
	 *         misma pantalla de dodne fue invocada, retornado el/los mensaje/s
	 *         de error.
	 * 
	 * @see NuevaAreaComando
	 */
	public ModelAndView nuevaArea(HttpServletRequest req, HttpServletResponse resp, NuevaAreaComando nuevaArea) {

		boolean flagErrores = getErrors().hasErrors();
		if (!flagErrores && nuevaArea.getCodigoArea() != 0) {
			flagErrores = !checkModificacionesPermitidas(nuevaArea);
		}

		if (!flagErrores) {
			flagErrores = checkArticulosYAreasAdyacentes(nuevaArea);
		}

		if (flagErrores) {
			req.setAttribute("errors", getErrors().getAllErrors());
			nuevaArea.setTipoDeArticulosSoportados(tipoDeArticuloDao.obtenerTodos());
			nuevaArea.setAlmacenes(almacenDao.obtenerTodos());
			if (nuevaArea.getCodigoArea()>0)
				nuevaArea.setAreasAdyacentes(areaDao.obtenerPorCodigoAlmacenDiferentesA(nuevaArea.getCodigoAlmacen(),nuevaArea.getCodigoArea()));
			else
				nuevaArea.setAreasAdyacentes(areaDao.obtenerPorCodigoAlmacen(nuevaArea.getCodigoAlmacen()));
			nuevaArea.setRangosDeTemperatura(rangoDeTemperaturaDao.obtenerTodos());
			nuevaArea.setPrioridades(prioridadDao.obtenerTodos());
			return new ModelAndView("nuevaArea.html", "command", nuevaArea);
		}

		RangoDeTemperatura rangoDeTemperatura = rangoDeTemperaturaDao.obtenerPorCodigo(nuevaArea
				.getCodigoRangoDeTamperatura());
		Prioridad prioridad = prioridadDao.obtenerPorCodigo(nuevaArea.getCodigoPrioridad());

		Area area = new Area();

		area.setCodigoArea(nuevaArea.getCodigoArea());
		area.setNombre(nuevaArea.getNombre());
		area.setAlmacen(almacenDao.obtenerAlmacenPorCodigo(nuevaArea.getCodigoAlmacen()));
		area.setAccesoRestringido(nuevaArea.isAccesoRestringido());
		area.setBajoTecho(nuevaArea.isBajoTecho());
		area.setRangoDeTemperatura(rangoDeTemperatura);
		area.setPrioridad(prioridad);

		area.quitarAreasAdyacentes();
		if (nuevaArea.getAreasAdyacentesSeleccionadas().length > 0) {
			List<Area> areasAdyacentes = areaDao.obtenerPorCodigos(nuevaArea.getAreasAdyacentesSeleccionadas());

			for (Area areaAdyacente : areasAdyacentes) {
				area.agregarAreaAdyacente(areaAdyacente);
			}
		}

		area.quitarTipoDeArticuloSoportado();
		if (nuevaArea.getTipoDeArticulosSoportadosSeleccionados().length > 0) {
			List<TipoDeArticulo> tiposDeArticulos = tipoDeArticuloDao.obtenerTiposDeArticuloPorId(nuevaArea
					.getTipoDeArticulosSoportadosSeleccionados());

			for (TipoDeArticulo tiposDeArticulosSoportados : tiposDeArticulos) {
				area.agregarTipoDeArticuloSoportado(tiposDeArticulosSoportados);
			}
		}

		areaDao.guardar(area);
		actualizarReferenciasEnAreasAdyacentes(area);

		return new ModelAndView("home.html");
	}

	private void actualizarReferenciasEnAreasAdyacentes(Area area) {
		if (area.getCodigoArea() > 0) {
			for (Area areaAdyacente : area.getAreasAdyacentes()) {
				if (!areaAdyacente.getAreasAdyacentes().contains(area)) {
					areaAdyacente.getAreasAdyacentes().add(area);
				}
				areaDao.guardar(areaAdyacente);
			}
		}
	}

	private boolean checkArticulosYAreasAdyacentes(NuevaAreaComando nuevaArea) {

		List<TipoDeArticulo> tiposDeArticulos = new ArrayList<TipoDeArticulo>();
		List<Area> areasAdyacentes = new ArrayList<Area>();
		if (nuevaArea.getTipoDeArticulosSoportadosSeleccionados().length > 0) {
			tiposDeArticulos = tipoDeArticuloDao.obtenerTiposDeArticuloPorId(nuevaArea
					.getTipoDeArticulosSoportadosSeleccionados());
		}
		if (nuevaArea.getAreasAdyacentesSeleccionadas().length > 0) {
			areasAdyacentes = areaDao.obtenerPorCodigos(nuevaArea.getAreasAdyacentesSeleccionadas());
		}
		List<ObjectError> errores = GestionarAreaHelper.validarTiposDeArticulos(tiposDeArticulos);

		for (Area area : areasAdyacentes) {
			ObjectError errorTipoDeArticulos = GestionarAreaHelper.validarTiposDeArticulos(tiposDeArticulos, area);
			if (errorTipoDeArticulos != null) {
				errores.add(errorTipoDeArticulos);
			}
		}

		for (ObjectError error : errores) {
			getErrors().addError(error);
		}
		return !errores.isEmpty();
	}

	private boolean checkModificacionesPermitidas(NuevaAreaComando nuevaArea) {
		Area area = areaDao.obtenerPorCodigo(nuevaArea.getCodigoArea());
		
		ObjectError checkAtributoAlmacenModificable = GestionarAreaHelper.checkAtributoAlmacenModificable(area, nuevaArea); 
		if (checkAtributoAlmacenModificable != null){
			getErrors().addError(checkAtributoAlmacenModificable);
			return false;
		}
		
		if (GestionarAreaHelper.checkAreaModificable(area.getUbicaciones())) {
			return true;
		}

		List<ObjectError> checkAtributosModificables = GestionarAreaHelper.checkAtributosModificables(area, nuevaArea);
		boolean result = checkAtributosModificables.isEmpty();
		for (ObjectError error : checkAtributosModificables) {
			getErrors().addError(error);
		}

		return result;

	}

	/**
	 * Acci&oacute:n para la busqueda de &aacute;reas.
	 * <p>
	 * Recupera los criterios de busqueda, y los aplica en una busqueda
	 * seg&uacute;n c&oacute;digo o almacen. Retorna una lista de las
	 * &aacute;reas que coinciden con los criterios de busqueda
	 * asyncorincamente, via un mensaje serializado seg&uacute;n estandar JSON.
	 * 
	 * @param req
	 *            Objeto HttpServletRequest que modela la peticion por parte de
	 *            la capa de presentaci&oacute;n.
	 * @param resp
	 *            Objeto HttpServletResponse que modela la respuesta del
	 *            servidor.
	 * @throws IOException
	 *             Si es que hay un error en la serializac&iacute;n de la
	 *             respuesta
	 */
	public void buscar(HttpServletRequest req, HttpServletResponse resp) throws IOException {

		int codigo;
		int almacenSeleccionada = 0;
		String nombre = req.getParameter("nombre");
		List<Area> areas;

		// TODO validar formato de numero
		if (StringUtils.isNotBlank(req.getParameter("codigoArea"))) {
			codigo = Integer.parseInt(req.getParameter("codigoArea"));
			areas = new ArrayList<Area>();
			Area area = areaDao.obtenerPorCodigo(codigo);
			if (area != null) {
				areas.add(area);
			}
		} else {
			if (StringUtils.isNotBlank(req.getParameter("almacenSeleccionada"))) {
				almacenSeleccionada = Integer.parseInt(req.getParameter("almacenSeleccionada"));
			}
			areas = areaDao.obtenerTodosBusqueda(nombre, almacenSeleccionada);
		}
		JSONObject jsonObj = new JSONObject();
		jsonObj.accumulate("areas", serializar(areas));
		PrintWriter writer = resp.getWriter();
		writer.append(jsonObj.toString());
	}

	private List<VistaAreaJson> serializar(List<Area> areas) {
		List<VistaAreaJson> areasSerializadas = new ArrayList<VistaAreaJson>();
		for (Area area : areas) {
			areasSerializadas.add(new VistaAreaJson(area));
		}
		return areasSerializadas;
	}

	private String[] obtenerArregloTipoDeArticulosSoportados(Set<TipoDeArticulo> tiposDeArticuloSoportados) {
		int cantidadItems = tiposDeArticuloSoportados.size();
		String[] items = new String[cantidadItems];

		int i = 0;
		for (TipoDeArticulo tipoDeArticuloSoportado : tiposDeArticuloSoportados) {
			Integer codigo = tipoDeArticuloSoportado.getCodigoTipoDeArticulo();
			items[i] = codigo.toString();
			i++;
		}

		return items;
	}

	private String[] obtenerArregloAreasAdyacentesSeleccionadas(Set<Area> areasAdyacentes) {
		int cantidadItems = areasAdyacentes.size();
		String[] items = new String[cantidadItems];

		int i = 0;
		for (Area areaAdyacente : areasAdyacentes) {
			Integer codigo = areaAdyacente.getCodigoArea();
			items[i] = codigo.toString();
			i++;
		}

		return items;
	}

	/**
	 * Acci&oacute:n para la modificacion de &aacute;reas.
	 * <p>
	 * Recupera el c&oacute;digo del &aacute;rea, y apartir de este recupera la
	 * instancia de la misma, y popula el comando <code>NuevaAreaComando</code>
	 * con sus datos.
	 * 
	 * @param req
	 *            Objeto HttpServletRequest que modela la peticion por parte de
	 *            la capa de presentaci&oacute;n.
	 * @param resp
	 *            Objeto HttpServletResponse que modela la respuesta del
	 *            servidor.
	 * @param BuscarAreaComando
	 *            Comando con los datos ingresados en para la modificacion.
	 * 
	 * @return ModelAndView Objecto que modela la Vista a retornar para la capa
	 *         de presentacion: en este caso particular es la pantalla de
	 *         <code>nuevaArea.html</code>.
	 */
	public ModelAndView modificar(HttpServletRequest req, HttpServletResponse resp) {

		BuscarAreaComando buscarAreaComando = obtenerComando(req);

		Area area = areaDao.obtenerPorCodigo(buscarAreaComando.getCodigoAOperar());

		String[] areasAdyacentesSeleccionadas = obtenerArregloAreasAdyacentesSeleccionadas(area.getAreasAdyacentes());
		String[] tipoDeArticulosSoportadosSeleccionados = obtenerArregloTipoDeArticulosSoportados(area
				.getTipoDeArticulosSoportados());

		NuevaAreaComando nueva = new NuevaAreaComando();

		nueva.setCodigoArea(area.getCodigoArea());
		nueva.setAccesoRestringido(area.isAccesoRestringido());
		nueva.setBajoTecho(area.isBajoTecho());
		nueva.setNombre(area.getNombre());
		nueva.setCodigoPrioridad(area.getPrioridad().getCodigoPrioridad());
		nueva.setCodigoRangoDeTamperatura(area.getRangoDeTemperatura().getCodigoRangoDeTemperatura());
		nueva.setCodigoAlmacen(area.getAlmacen().getCodigoAlmacen());

		nueva.setAreasAdyacentesSeleccionadas(areasAdyacentesSeleccionadas);
		nueva.setTipoDeArticulosSoportadosSeleccionados(tipoDeArticulosSoportadosSeleccionados);

		nueva.setAreasAdyacentes(areaDao.obtenerPorCodigoAlmacenDiferentesA(nueva.getCodigoAlmacen(), nueva
				.getCodigoArea()));
		nueva.setTipoDeArticulosSoportados(tipoDeArticuloDao.obtenerTodos());
		nueva.setAlmacenes(almacenDao.obtenerTodos());
		nueva.setRangosDeTemperatura(rangoDeTemperaturaDao.obtenerTodos());
		nueva.setPrioridades(prioridadDao.obtenerTodos());

		return new ModelAndView("nuevaArea.html", "command", nueva);
	}

	private BuscarAreaComando obtenerComando(HttpServletRequest req) {
		BuscarAreaComando buscarAreaComando = new BuscarAreaComando();
		ServletRequestDataBinder binder = new ServletRequestDataBinder(buscarAreaComando);
		binder.bind(req);
		return buscarAreaComando;
	}
	
	/**
	 * Acci&oacute:n para la eliminaci&oacute;n de un &aacute;rea.
	 * <p>
	 * Recupera el c&oacute:digo a fin de obtener la intancia asociada desde la
	 * base da datos y la eliminar logicamente, persistendo el cambio en la base
	 * de datos.
	 * <p>
	 * Informa asyncorincamente el resultado de la eliminaci&oacute;n, via un
	 * mensaje serializado seg&uacute;n estandar JSON.
	 * 
	 * @param req
	 *            Objeto HttpServletRequest que modela la peticion por parte de
	 *            la capa de presentaci&oacute;n.
	 * @param resp
	 *            Objeto HttpServletResponse que modela la respuesta del
	 *            servidor.
	 * @throws IOException
	 *             Si es que hay un error en la serializac&iacute;n de la
	 *             respuesta
	 */
	public void eliminar(HttpServletRequest req, HttpServletResponse resp) throws IOException {
		int codigo = Integer.parseInt(req.getParameter("codigo"));

		Area area = areaDao.obtenerPorCodigo(codigo);
		area.marcarComoBorrada();

		areaDao.guardar(area);

		JSONObject jsonObj = new JSONObject();
		jsonObj.accumulate("exito", true);
		PrintWriter writer = resp.getWriter();
		writer.append(jsonObj.toString());
	}

	/**
	 * Determina a partir del c&oacute;digo de un &aacute;rea si puede o no ser
	 * borrada.
	 * 
	 * @param req
	 *            Objeto HttpServletRequest que modela la peticion por parte de
	 *            la capa de presentaci&oacute;n.
	 * @param resp
	 *            Objeto HttpServletResponse que modela la respuesta del
	 *            servidor.
	 * 
	 * @throws IOException
	 *             Si es que hay un error en la serializac&iacute;n de la
	 *             respuesta
	 */
	public void puedoBorrarArea(HttpServletRequest req, HttpServletResponse resp) throws IOException {
		int codigo = Integer.parseInt(req.getParameter("codigo"));

		Area area = areaDao.obtenerPorCodigo(codigo);

		JSONObject jsonObj = new JSONObject();
		jsonObj.accumulate("puedeSerBorrada", area.puedeSerBorrada());
		PrintWriter writer = resp.getWriter();
		writer.append(jsonObj.toString());
	}
	
	public void areasPorAlmacenes(HttpServletRequest req, HttpServletResponse resp) throws IOException {
		int codigoAlmacen = Integer.parseInt(req.getParameter("codigo"));
		int codigoArea = 0;
		
		if (StringUtils.isNotBlank(req.getParameter("codigoArea"))) {
			codigoArea = Integer.parseInt(req.getParameter("codigoArea"));
		}
		
		List<Area> areas;
		
		//Si es una modificacion no debe mostrar esta area
		if(codigoArea > 0){
			areas = areaDao.obtenerPorCodigoAlmacenDiferentesA(codigoAlmacen,codigoArea);
		}else{
			areas = areaDao.obtenerPorCodigoAlmacen(codigoAlmacen);
		}
		
		JSONObject jsonObj = serializarAreas(areas, true);
		PrintWriter writer = resp.getWriter();
		writer.append(jsonObj.toString());
	}
	
	private JSONObject serializarAreas(List<Area> areas, boolean errorFlag) {
		JSONObject jsonObj = new JSONObject();
		if (errorFlag){
			List<VistaComboJson> vistaAreas = new ArrayList<VistaComboJson>();
			for (Area area : areas) {
				vistaAreas.add(new VistaComboJson(area.getCodigoArea(), area.getNombre()));
			}
			if (!vistaAreas.isEmpty()) {
				jsonObj.accumulate("exito", true);
				jsonObj.accumulate("items", vistaAreas);
				return jsonObj;
			}
		}
		jsonObj.accumulate("exito", false);
		return jsonObj;
	}

	public void setAreaDao(AreaDao areaDao) {
		this.areaDao = areaDao;
	}

	public void setTipoDeArticuloDao(TipoDeArticuloDao tipoDeArticuloDao) {
		this.tipoDeArticuloDao = tipoDeArticuloDao;
	}

	public void setAlmacenDao(AlmacenDao almacenDao) {
		this.almacenDao = almacenDao;
	}

	public void setPrioridadDao(PrioridadDao prioridadDao) {
		this.prioridadDao = prioridadDao;
	}

	public void setRangoDeTemperaturaDao(RangoDeTemperaturaDao rangoDeTemperaturaDao) {
		this.rangoDeTemperaturaDao = rangoDeTemperaturaDao;
	}
}
